#include "node.h"
#include "node_file.h"
#include "node_buffer.h"
#include "node_internals.h"
#include "node_stat_watcher.h"

#include "env.h"
#include "env-inl.h"
#include "req-wrap.h"
#include "req-wrap-inl.h"
#include "string_bytes.h"
#include "util.h"

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <limits.h>

#if defined(__MINGW32__) || defined(_MSC_VER)
#include <io.h>
#endif

#include <vector>

namespace node {

using v8::Array;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Integer;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

#define TYPE_ERROR(msg) env->ThrowTypeError(msg)

#define GET_OFFSET(a) ((a)->IsNumber() ? (a)->IntegerValue() : -1)

class FSReqWrap : public ReqWrap<uv_fs_t> {
public:
    enum Ownership { COPY,
        MOVE };

    inline static FSReqWrap* New(Environment* env,
        Local<Object> req,
        const char* syscall,
        const char* data = nullptr,
        enum encoding encoding = UTF8,
        Ownership ownership = COPY);

    inline void Dispose();

    void ReleaseEarly()
    {
        if (data_ != inline_data()) {
            delete[] data_;
            data_ = nullptr;
        }
    }

    const char* syscall() const { return syscall_; }
    const char* data() const { return data_; }
    const enum encoding encoding_;

    size_t self_size() const override { return sizeof(*this); }

private:
    FSReqWrap(Environment* env,
        Local<Object> req,
        const char* syscall,
        const char* data,
        enum encoding encoding)
        : ReqWrap(env, req, AsyncWrap::PROVIDER_FSREQWRAP)
        , encoding_(encoding)
        , syscall_(syscall)
        , data_(data)
    {
        Wrap(object(), this);
    }

    ~FSReqWrap() { ReleaseEarly(); }

    void* operator new(size_t size) = delete;
    void* operator new(size_t size, char* storage) { return storage; }
    char* inline_data() { return reinterpret_cast<char*>(this + 1); }

    const char* syscall_;
    const char* data_;

    NODEJS_DISALLOW_COPY_AND_ASSIGN(FSReqWrap);
};

#define NODE_ASSERT_PATH(path) \
    if (*path == nullptr)      \
        return TYPE_ERROR(#path " must be a string or Buffer");

FSReqWrap* FSReqWrap::New(Environment* env,
    Local<Object> req,
    const char* syscall,
    const char* data,
    enum encoding encoding,
    Ownership ownership)
{
    const bool copy = (data != nullptr && ownership == COPY);
    const size_t size = copy ? 1 + strlen(data) : 0;
    FSReqWrap* that;
    char* const storage = new char[sizeof(*that) + size];
    that = new (storage) FSReqWrap(env, req, syscall, data, encoding);
    if (copy)
        that->data_ = static_cast<char*>(memcpy(that->inline_data(), data, size));
    return that;
}

void FSReqWrap::Dispose()
{
    this->~FSReqWrap();
    delete[] reinterpret_cast<char*>(this);
}

static void NewFSReqWrap(const FunctionCallbackInfo<Value>& args)
{
    NODE_CHECK(args.IsConstructCall());
}

static inline bool IsInt64(double x)
{
    return x == static_cast<double>(static_cast<int64_t>(x));
}

static void After(uv_fs_t* req)
{
    FSReqWrap* req_wrap = static_cast<FSReqWrap*>(req->data);
    NODE_CHECK_EQ(req_wrap->req(), req);
    req_wrap->ReleaseEarly(); // Free memory that's no longer used now.

    Environment* env = req_wrap->env();
    HandleScope handle_scope(env->isolate());
    Context::Scope context_scope(env->context());

#ifndef MINIBLINK_NOT_IMPLEMENTED
    if (env->is_blink_core())
        env->BlinkMicrotaskSuppressionEnter(env);
#endif

    // there is always at least one argument. "error"
    int argc = 1;

    // Allocate space for two args. We may only use one depending on the case.
    // (Feel free to increase this if you need more)
    Local<Value> argv[2];
    Local<Value> link;

    if (req->result < 0) {
        // An error happened.
        argv[0] = UVException(env->isolate(),
            req->result,
            req_wrap->syscall(),
            nullptr,
            req->path,
            req_wrap->data());
    } else {
        // error value is empty or null for non-error.
        argv[0] = Null(env->isolate());

        // All have at least two args now.
        argc = 2;

        switch (req->fs_type) {
        // These all have no data to pass.
        case UV_FS_ACCESS:
        case UV_FS_CLOSE:
        case UV_FS_RENAME:
        case UV_FS_UNLINK:
        case UV_FS_RMDIR:
        case UV_FS_MKDIR:
        case UV_FS_FTRUNCATE:
        case UV_FS_FSYNC:
        case UV_FS_FDATASYNC:
        case UV_FS_LINK:
        case UV_FS_SYMLINK:
        case UV_FS_CHMOD:
        case UV_FS_FCHMOD:
        case UV_FS_CHOWN:
        case UV_FS_FCHOWN:
            // These, however, don't.
            argc = 1;
            break;

        case UV_FS_UTIME:
        case UV_FS_FUTIME:
            argc = 0;
            break;

        case UV_FS_OPEN:
            argv[1] = Integer::New(env->isolate(), req->result);
            break;

        case UV_FS_WRITE:
            argv[1] = Integer::New(env->isolate(), req->result);
            break;

        case UV_FS_STAT:
        case UV_FS_LSTAT:
        case UV_FS_FSTAT:
            argv[1] = BuildStatsObject(env,
                static_cast<const uv_stat_t*>(req->ptr));
            break;

        case UV_FS_MKDTEMP:
            link = StringBytes::Encode(env->isolate(),
                static_cast<const char*>(req->path),
                req_wrap->encoding_);
            if (link.IsEmpty()) {
                argv[0] = UVException(env->isolate(),
                    UV_EINVAL,
                    req_wrap->syscall(),
                    "Invalid character encoding for filename",
                    req->path,
                    req_wrap->data());
            } else {
                argv[1] = link;
            }
            break;

        case UV_FS_READLINK:
            link = StringBytes::Encode(env->isolate(),
                static_cast<const char*>(req->ptr),
                req_wrap->encoding_);
            if (link.IsEmpty()) {
                argv[0] = UVException(env->isolate(),
                    UV_EINVAL,
                    req_wrap->syscall(),
                    "Invalid character encoding for link",
                    req->path,
                    req_wrap->data());
            } else {
                argv[1] = link;
            }
            break;

        case UV_FS_REALPATH:
            link = StringBytes::Encode(env->isolate(),
                static_cast<const char*>(req->ptr),
                req_wrap->encoding_);
            if (link.IsEmpty()) {
                argv[0] = UVException(env->isolate(),
                    UV_EINVAL,
                    req_wrap->syscall(),
                    "Invalid character encoding for link",
                    req->path,
                    req_wrap->data());
            } else {
                argv[1] = link;
            }
            break;

        case UV_FS_READ:
            // Buffer interface
            argv[1] = Integer::New(env->isolate(), req->result);
            break;

        case UV_FS_SCANDIR: {
            int r;
            Local<Array> names = Array::New(env->isolate(), 0);
            Local<Function> fn = env->push_values_to_array_function();
            Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
            size_t name_idx = 0;

            for (int i = 0;; i++) {
                uv_dirent_t ent;

                r = uv_fs_scandir_next(req, &ent);
                if (r == UV_EOF)
                    break;
                if (r != 0) {
                    argv[0] = UVException(r,
                        nullptr,
                        req_wrap->syscall(),
                        static_cast<const char*>(req->path));
                    break;
                }

                Local<Value> filename = StringBytes::Encode(env->isolate(),
                    ent.name,
                    req_wrap->encoding_);
                if (filename.IsEmpty()) {
                    argv[0] = UVException(env->isolate(),
                        UV_EINVAL,
                        req_wrap->syscall(),
                        "Invalid character encoding for filename",
                        req->path,
                        req_wrap->data());
                    break;
                }
                name_argv[name_idx++] = filename;

                if (name_idx >= arraysize(name_argv)) {
                    fn->Call(env->context(), names, name_idx, name_argv)
                        .ToLocalChecked();
                    name_idx = 0;
                }
            }

            if (name_idx > 0) {
                fn->Call(env->context(), names, name_idx, name_argv)
                    .ToLocalChecked();
            }

            argv[1] = names;
        } break;

        default:
            NODE_CHECK(0 && "Unhandled eio response");
        }
    }

    req_wrap->MakeCallback(env->oncomplete_string(), argc, argv);

#ifndef MINIBLINK_NOT_IMPLEMENTED
    if (env->is_blink_core())
        env->BlinkMicrotaskSuppressionLeave(env);
#endif

    uv_fs_req_cleanup(req_wrap->req());
    req_wrap->Dispose();
}

// This struct is only used on sync fs calls.
// For async calls FSReqWrap is used.
class fs_req_wrap {
public:
    fs_req_wrap() { }
    ~fs_req_wrap() { uv_fs_req_cleanup(&req); }
    uv_fs_t req;

private:
    NODEJS_DISALLOW_COPY_AND_ASSIGN(fs_req_wrap);
};

#define ASYNC_DEST_CALL(func, request, dest, encoding, ...)         \
    Environment* env = Environment::GetCurrent(args);               \
    NODE_CHECK(request->IsObject());                                \
    FSReqWrap* req_wrap = FSReqWrap::New(env, request.As<Object>(), \
        #func, dest, encoding);                                     \
    int err = uv_fs_##func(env->event_loop(),                       \
        req_wrap->req(),                                            \
        __VA_ARGS__,                                                \
        After);                                                     \
    req_wrap->Dispatched();                                         \
    if (err < 0) {                                                  \
        uv_fs_t* uv_req = req_wrap->req();                          \
        uv_req->result = err;                                       \
        uv_req->path = nullptr;                                     \
        After(uv_req);                                              \
        req_wrap = nullptr;                                         \
    } else {                                                        \
        args.GetReturnValue().Set(req_wrap->persistent());          \
    }

#define ASYNC_CALL(func, req, encoding, ...) \
    ASYNC_DEST_CALL(func, req, nullptr, encoding, __VA_ARGS__)

#define SYNC_DEST_CALL(func, path, dest, ...)                          \
    fs_req_wrap req_wrap;                                              \
    env->PrintSyncTrace();                                             \
    int err = uv_fs_##func(env->event_loop(),                          \
        &req_wrap.req,                                                 \
        __VA_ARGS__,                                                   \
        nullptr);                                                      \
    if (err < 0) {                                                     \
        return env->ThrowUVException(err, #func, nullptr, path, dest); \
    }

#define SYNC_CALL(func, path, ...) \
    SYNC_DEST_CALL(func, path, nullptr, __VA_ARGS__)

#define SYNC_REQ req_wrap.req

#define SYNC_RESULT err

static void Access(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args.GetIsolate());
    HandleScope scope(env->isolate());

    if (args.Length() < 2)
        return TYPE_ERROR("path and mode are required");
    if (!args[1]->IsInt32())
        return TYPE_ERROR("mode must be an integer");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    int mode = static_cast<int>(args[1]->Int32Value());

    if (args[2]->IsObject()) {
        ASYNC_CALL(access, args[2], UTF8, *path, mode);
    } else {
        SYNC_CALL(access, *path, *path, mode);
    }
}

static void Close(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 1)
        return TYPE_ERROR("fd is required");
    if (!args[0]->IsInt32())
        return TYPE_ERROR("fd must be a file descriptor");

    int fd = args[0]->Int32Value();

    if (args[1]->IsObject()) {
        ASYNC_CALL(close, args[1], UTF8, fd)
    } else {
        SYNC_CALL(close, 0, fd)
    }
}

Local<Value> BuildStatsObject(Environment* env, const uv_stat_t* s)
{
    EscapableHandleScope handle_scope(env->isolate());

    // If you hit this NODE_ASSERTion, you forgot to enter the v8::Context first.
    NODE_CHECK_EQ(env->context(), env->isolate()->GetCurrentContext());

    // The code below is very nasty-looking but it prevents a segmentation fault
    // when people run JS code like the snippet below. It's apparently more
    // common than you would expect, several people have reported this crash...
    //
    //   function crash() {
    //     fs.statSync('.');
    //     crash();
    //   }
    //
    // We need to check the return value of Number::New() and Date::New()
    // and make sure that we bail out when V8 returns an empty handle.

    // Unsigned integers. It does not actually seem to be specified whether
    // uid and gid are unsigned or not, but in practice they are unsigned,
    // and Node’s (F)Chown functions do check their arguments for unsignedness.
#define X(name)                                                                 \
    Local<Value> name = Integer::NewFromUnsigned(env->isolate(), s->st_##name); \
    if (name.IsEmpty())                                                         \
        return Local<Object>();

    X(uid)
    X(gid)
#if defined(__POSIX__)
    X(blksize)
#else
    Local<Value> blksize = Undefined(env->isolate());
#endif
#undef X

    // Integers.
#define X(name)                                                     \
    Local<Value> name = Integer::New(env->isolate(), s->st_##name); \
    if (name.IsEmpty())                                             \
        return Local<Object>();

    X(dev)
    X(mode)
    X(nlink)
    X(rdev)
#undef X

    // Numbers.
#define X(name)                                     \
    Local<Value> name = Number::New(env->isolate(), \
        static_cast<double>(s->st_##name));         \
    if (name.IsEmpty())                             \
        return Local<Object>();

    X(ino)
    X(size)
#if defined(__POSIX__)
    X(blocks)
#else
    Local<Value> blocks = Undefined(env->isolate());
#endif
#undef X

    // Dates.
#define X(name)                                                                                                     \
    Local<Value> name##_msec = Number::New(env->isolate(),                                                          \
        (static_cast<double>(s->st_##name.tv_sec) * 1000) + (static_cast<double>(s->st_##name.tv_nsec / 1000000))); \
                                                                                                                    \
    if (name##_msec.IsEmpty())                                                                                      \
        return Local<Object>();

    X(atim)
    X(mtim)
    X(ctim)
    X(birthtim)
#undef X

    // Pass stats as the first argument, this is the object we are modifying.
    Local<Value> argv[] = {
        dev,
        mode,
        nlink,
        uid,
        gid,
        rdev,
        blksize,
        ino,
        size,
        blocks,
        atim_msec,
        mtim_msec,
        ctim_msec,
        birthtim_msec
    };

#ifndef MINIBLINK_NOT_IMPLEMENTED
    if (env->is_blink_core())
        env->BlinkMicrotaskSuppressionEnter(env);
#endif

    // Call out to JavaScript to create the stats object.
    Local<Value> stats = env->fs_stats_constructor_function()->NewInstance(
                                                                 env->context(),
                                                                 arraysize(argv),
                                                                 argv)
                             .FromMaybe(Local<Value>());
#ifndef MINIBLINK_NOT_IMPLEMENTED
    if (env->is_blink_core())
        env->BlinkMicrotaskSuppressionLeave(env);
#endif
    if (stats.IsEmpty())
        return handle_scope.Escape(Local<Object>());

    return handle_scope.Escape(stats);
}

// Used to speed up module loading.  Returns the contents of the file as
// a string or undefined when the file cannot be opened.  The speedup
// comes from not creating Error objects on failure.
static void InternalModuleReadFile(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);
    uv_loop_t* loop = env->event_loop();

    NODE_CHECK(args[0]->IsString());
    node::Utf8Value path(env->isolate(), args[0]);

    uv_fs_t open_req;
    const int fd = uv_fs_open(loop, &open_req, *path, O_RDONLY, 0, nullptr);
    uv_fs_req_cleanup(&open_req);

    if (fd < 0) {
        return;
    }

    std::vector<char> chars;
    int64_t offset = 0;
    for (;;) {
        const size_t kBlockSize = 32 << 10;
        const size_t start = chars.size();
        chars.resize(start + kBlockSize);

        uv_buf_t buf;
        buf.base = &chars[start];
        buf.len = kBlockSize;

        uv_fs_t read_req;
        const ssize_t numchars = uv_fs_read(loop, &read_req, fd, &buf, 1, offset, nullptr);
        uv_fs_req_cleanup(&read_req);

        NODE_CHECK_GE(numchars, 0);
        if (static_cast<size_t>(numchars) < kBlockSize) {
            chars.resize(start + numchars);
        }
        if (numchars == 0) {
            break;
        }
        offset += numchars;
    }

    uv_fs_t close_req;
    NODE_CHECK_EQ(0, uv_fs_close(loop, &close_req, fd, nullptr));
    uv_fs_req_cleanup(&close_req);

    size_t start = 0;
    if (chars.size() >= 3 && 0 == memcmp(&chars[0], "\xEF\xBB\xBF", 3)) {
        start = 3; // Skip UTF-8 BOM.
    }

    Local<String> chars_string = String::NewFromUtf8(env->isolate(),
        &chars[start],
        String::kNormalString,
        chars.size() - start);
    args.GetReturnValue().Set(chars_string);
}

// Used to speed up module loading.  Returns 0 if the path refers to
// a file, 1 when it's a directory or < 0 on error (usually -ENOENT.)
// The speedup comes from not creating thousands of Stat and Error objects.
static void InternalModuleStat(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    NODE_CHECK(args[0]->IsString());
    node::Utf8Value path(env->isolate(), args[0]);
    if (env->file_system_hooks()) {
        int rc;
        if (env->file_system_hooks()->internalModuleStat(*path, &rc)) {
            args.GetReturnValue().Set(rc);
            return;
        }
    }
    uv_fs_t req;
    int rc = uv_fs_stat(env->event_loop(), &req, *path, nullptr);
    if (rc == 0) {
        const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr);
        rc = !!(s->st_mode & S_IFDIR);
    }
    uv_fs_req_cleanup(&req);

    args.GetReturnValue().Set(rc);
}

static void Stat(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 1)
        return TYPE_ERROR("path required");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    if (args[1]->IsObject()) {
        ASYNC_CALL(stat, args[1], UTF8, *path)
    } else {
        SYNC_CALL(stat, *path, *path)
        args.GetReturnValue().Set(
            BuildStatsObject(env, static_cast<const uv_stat_t*>(SYNC_REQ.ptr)));
    }
}

static void StatNoException(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args.GetIsolate());
    HandleScope scope(env->isolate());

    if (args.Length() < 1 || !args[0]->IsString()) {
        args.GetReturnValue().Set(v8::Boolean::New(env->isolate(), false));
        return;
    }

    String::Utf8Value path(args[0]);

    fs_req_wrap req_wrap;
    int result = uv_fs_stat(uv_default_loop(), &req_wrap.req, *path, NULL);
    if (result < 0)
        args.GetReturnValue().Set(v8::Boolean::New(env->isolate(), false));
    else
        args.GetReturnValue().Set(BuildStatsObject(env, static_cast<const uv_stat_t*>(SYNC_REQ.ptr)));
}

static void LStat(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 1)
        return TYPE_ERROR("path required");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    if (args[1]->IsObject()) {
        ASYNC_CALL(lstat, args[1], UTF8, *path)
    } else {
        SYNC_CALL(lstat, *path, *path)
        args.GetReturnValue().Set(
            BuildStatsObject(env, static_cast<const uv_stat_t*>(SYNC_REQ.ptr)));
    }
}

static void LStatNoException(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args.GetIsolate());
    HandleScope scope(env->isolate());

    String::Utf8Value path(args[0]);

    if (args.Length() < 1 || !args[0]->IsString()) {
        args.GetReturnValue().Set(v8::Boolean::New(env->isolate(), false));
        return;
    }

    fs_req_wrap req_wrap;
    int result = uv_fs_lstat(uv_default_loop(), &req_wrap.req, *path, NULL);
    if (result < 0)
        args.GetReturnValue().Set(v8::Boolean::New(env->isolate(), false));
    else
        args.GetReturnValue().Set(
            BuildStatsObject(env, static_cast<const uv_stat_t*>(SYNC_REQ.ptr)));
}

static void FStat(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 1)
        return TYPE_ERROR("fd is required");
    if (!args[0]->IsInt32())
        return TYPE_ERROR("fd must be a file descriptor");

    int fd = args[0]->Int32Value();

    if (args[1]->IsObject()) {
        ASYNC_CALL(fstat, args[1], UTF8, fd)
    } else {
        SYNC_CALL(fstat, 0, fd)
        args.GetReturnValue().Set(
            BuildStatsObject(env, static_cast<const uv_stat_t*>(SYNC_REQ.ptr)));
    }
}

static void Symlink(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    int len = args.Length();
    if (len < 1)
        return TYPE_ERROR("target path required");
    if (len < 2)
        return TYPE_ERROR("src path required");

    BufferValue target(env->isolate(), args[0]);
    NODE_ASSERT_PATH(target)
    BufferValue path(env->isolate(), args[1]);
    NODE_ASSERT_PATH(path)

    int flags = 0;

    if (args[2]->IsString()) {
        node::Utf8Value mode(env->isolate(), args[2]);
        if (strcmp(*mode, "dir") == 0) {
            flags |= UV_FS_SYMLINK_DIR;
        } else if (strcmp(*mode, "junction") == 0) {
            flags |= UV_FS_SYMLINK_JUNCTION;
        } else if (strcmp(*mode, "file") != 0) {
            return env->ThrowError("Unknown symlink type");
        }
    }

    if (args[3]->IsObject()) {
        ASYNC_DEST_CALL(symlink, args[3], *path, UTF8, *target, *path, flags)
    } else {
        SYNC_DEST_CALL(symlink, *target, *path, *target, *path, flags)
    }
}

static void Link(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    int len = args.Length();
    if (len < 1)
        return TYPE_ERROR("src path required");
    if (len < 2)
        return TYPE_ERROR("dest path required");

    BufferValue src(env->isolate(), args[0]);
    NODE_ASSERT_PATH(src)

    BufferValue dest(env->isolate(), args[1]);
    NODE_ASSERT_PATH(dest)

    if (args[2]->IsObject()) {
        ASYNC_DEST_CALL(link, args[2], *dest, UTF8, *src, *dest)
    } else {
        SYNC_DEST_CALL(link, *src, *dest, *src, *dest)
    }
}

static void ReadLink(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    const int argc = args.Length();

    if (argc < 1)
        return TYPE_ERROR("path required");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);

    Local<Value> callback = Null(env->isolate());
    if (argc == 3)
        callback = args[2];

    if (callback->IsObject()) {
        ASYNC_CALL(readlink, callback, encoding, *path)
    } else {
        SYNC_CALL(readlink, *path, *path)
        const char* link_path = static_cast<const char*>(SYNC_REQ.ptr);
        Local<Value> rc = StringBytes::Encode(env->isolate(),
            link_path,
            encoding);
        if (rc.IsEmpty()) {
            return env->ThrowUVException(UV_EINVAL,
                "readlink",
                "Invalid character encoding for link",
                *path);
        }
        args.GetReturnValue().Set(rc);
    }
}

static void Rename(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    int len = args.Length();
    if (len < 1)
        return TYPE_ERROR("old path required");
    if (len < 2)
        return TYPE_ERROR("new path required");

    BufferValue old_path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(old_path)
    BufferValue new_path(env->isolate(), args[1]);
    NODE_ASSERT_PATH(new_path)

    if (args[2]->IsObject()) {
        ASYNC_DEST_CALL(rename, args[2], *new_path, UTF8, *old_path, *new_path)
    } else {
        SYNC_DEST_CALL(rename, *old_path, *new_path, *old_path, *new_path)
    }
}

static void FTruncate(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 2)
        return TYPE_ERROR("fd and length are required");
    if (!args[0]->IsInt32())
        return TYPE_ERROR("fd must be a file descriptor");

    int fd = args[0]->Int32Value();

    // FIXME(bnoordhuis) It's questionable to reject non-ints here but still
    // allow implicit coercion from null or undefined to zero.  Probably best
    // handled in lib/fs.js.
    Local<Value> len_v(args[1]);
    if (!len_v->IsUndefined() && !len_v->IsNull() && !IsInt64(len_v->NumberValue())) {
        return env->ThrowTypeError("Not an integer");
    }

    const int64_t len = len_v->IntegerValue();

    if (args[2]->IsObject()) {
        ASYNC_CALL(ftruncate, args[2], UTF8, fd, len)
    } else {
        SYNC_CALL(ftruncate, 0, fd, len)
    }
}

static void Fdatasync(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 1)
        return TYPE_ERROR("fd is required");
    if (!args[0]->IsInt32())
        return TYPE_ERROR("fd must be a file descriptor");

    int fd = args[0]->Int32Value();

    if (args[1]->IsObject()) {
        ASYNC_CALL(fdatasync, args[1], UTF8, fd)
    } else {
        SYNC_CALL(fdatasync, 0, fd)
    }
}

static void Fsync(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 1)
        return TYPE_ERROR("fd is required");
    if (!args[0]->IsInt32())
        return TYPE_ERROR("fd must be a file descriptor");

    int fd = args[0]->Int32Value();

    if (args[1]->IsObject()) {
        ASYNC_CALL(fsync, args[1], UTF8, fd)
    } else {
        SYNC_CALL(fsync, 0, fd)
    }
}

static void Unlink(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 1)
        return TYPE_ERROR("path required");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    if (args[1]->IsObject()) {
        ASYNC_CALL(unlink, args[1], UTF8, *path)
    } else {
        SYNC_CALL(unlink, *path, *path)
    }
}

static void RMDir(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 1)
        return TYPE_ERROR("path required");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    if (args[1]->IsObject()) {
        ASYNC_CALL(rmdir, args[1], UTF8, *path)
    } else {
        SYNC_CALL(rmdir, *path, *path)
    }
}

static void MKDir(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 2)
        return TYPE_ERROR("path and mode are required");
    if (!args[1]->IsInt32())
        return TYPE_ERROR("mode must be an integer");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    int mode = static_cast<int>(args[1]->Int32Value());

    if (args[2]->IsObject()) {
        ASYNC_CALL(mkdir, args[2], UTF8, *path, mode)
    } else {
        SYNC_CALL(mkdir, *path, *path, mode)
    }
}

#if defined(WIN32)
std::string utf16ToUtf8(LPCWSTR lpszSrc)
{
    std::string sResult;
    if (lpszSrc != NULL) {
        int nUTF8Len = WideCharToMultiByte(CP_UTF8, 0, lpszSrc, -1, NULL, 0, NULL, NULL);
        char* pUTF8 = new char[nUTF8Len + 1];
        if (pUTF8 != NULL) {
            ZeroMemory(pUTF8, nUTF8Len + 1);
            WideCharToMultiByte(CP_UTF8, 0, lpszSrc, -1, pUTF8, nUTF8Len, NULL, NULL);
            sResult = pUTF8;
            delete[] pUTF8;
        }
    }
    return sResult;
}

std::wstring utf8ToUtf16(const std::string& utf8)
{
    std::wstring utf16;
    size_t n = ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), nullptr, 0);
    if (0 == n)
        return L"";
    std::vector<wchar_t> wbuf(n);
    ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), &wbuf[0], n);
    utf16.resize(n + 5);
    utf16.assign(&wbuf[0], n);
    return utf16;
}
#endif

static void GetLongPathSync(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);
    const int argc = args.Length();

    if (argc < 1)
        return TYPE_ERROR("path required");
    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path);

    std::string longPathUtf8;
#if defined(WIN32)
    std::wstring pathW = utf8ToUtf16(std::string(path.out(), path.length()));

    std::vector<WCHAR> longPath(MAX_PATH + 1);
    DWORD result = GetLongPathNameW(pathW.c_str(), longPath.data(), longPath.size());
    if (0 == result) {
        args.GetReturnValue().Set(args[0]);
    }
    longPathUtf8 = utf16ToUtf8(longPath.data());
#else
    longPathUtf8 = std::string(path.out(), path.length());
#endif

    Local<Value> rc = StringBytes::Encode(env->isolate(), longPathUtf8.c_str(), UTF8);
    args.GetReturnValue().Set(rc);
}

static void CopyFile(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);
    v8::Isolate* isolate = env->isolate();

    const int argc = args.Length();
    //CHECK_GE(argc, 3);

    BufferValue src(isolate, args[0]);
    //CHECK_NOT_NULL(*src);
#if defined(WIN32)
    std::wstring srcW = utf8ToUtf16(std::string(src.out(), src.length()));

    BufferValue dest(isolate, args[1]);
    //CHECK_NOT_NULL(*dest);
    std::wstring destW = utf8ToUtf16(std::string(dest.out(), dest.length()));

    //CHECK(args[2]->IsInt32());
    //const int flags = args[2].As<Int32>()->Value();

    BOOL b = ::CopyFile(srcW.c_str(), destW.c_str(), FALSE);

#else
    __debugbreak();
#endif

    //     FSReqBase* req_wrap_async = GetReqWrap(env, args[3]);
    //     if (req_wrap_async != nullptr) {  // copyFile(src, dest, flags, req)
    //         AsyncDestCall(env, req_wrap_async, args, "copyfile",
    //             *dest, dest.length(), UTF8, AfterNoArgs,
    //             uv_fs_copyfile, *src, *dest, flags);
    //     } else {  // copyFile(src, dest, flags, undefined, ctx)
    //         CHECK_EQ(argc, 5);
    //         FSReqWrapSync req_wrap_sync;
    //         FS_SYNC_TRACE_BEGIN(copyfile);
    //         SyncCall(env, args[4], &req_wrap_sync, "copyfile",
    //             uv_fs_copyfile, *src, *dest, flags);
    //         FS_SYNC_TRACE_END(copyfile);
    //     }
}

static void RealPath(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    const int argc = args.Length();

    if (argc < 1)
        return TYPE_ERROR("path required");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);

    Local<Value> callback = Null(env->isolate());
    if (argc == 3)
        callback = args[2];

    if (callback->IsObject()) {
        ASYNC_CALL(realpath, callback, encoding, *path);
    } else {
        SYNC_CALL(realpath, *path, *path);
        const char* link_path = static_cast<const char*>(SYNC_REQ.ptr);
        Local<Value> rc = StringBytes::Encode(env->isolate(),
            link_path,
            encoding);
        if (rc.IsEmpty()) {
            return env->ThrowUVException(UV_EINVAL,
                "realpath",
                "Invalid character encoding for path",
                *path);
        }
        args.GetReturnValue().Set(rc);
    }
}

static void ReadDir(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    const int argc = args.Length();

    if (argc < 1)
        return TYPE_ERROR("path required");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);

    Local<Value> callback = Null(env->isolate());
    if (argc == 3)
        callback = args[2];

    if (callback->IsObject()) {
        ASYNC_CALL(scandir, callback, encoding, *path, 0 /*flags*/)
    } else {
        SYNC_CALL(scandir, *path, *path, 0 /*flags*/)

        NODE_CHECK_GE(SYNC_REQ.result, 0);
        int r;
        Local<Array> names = Array::New(env->isolate(), 0);
        Local<Function> fn = env->push_values_to_array_function();
        Local<Value> name_v[NODE_PUSH_VAL_TO_ARRAY_MAX];
        size_t name_idx = 0;

        for (int i = 0;; i++) {
            uv_dirent_t ent;

            r = uv_fs_scandir_next(&SYNC_REQ, &ent);
            if (r == UV_EOF)
                break;
            if (r != 0)
                return env->ThrowUVException(r, "readdir", "", *path);

            Local<Value> filename = StringBytes::Encode(env->isolate(),
                ent.name,
                encoding);
            if (filename.IsEmpty()) {
                return env->ThrowUVException(UV_EINVAL,
                    "readdir",
                    "Invalid character encoding for filename",
                    *path);
            }

            name_v[name_idx++] = filename;

            if (name_idx >= arraysize(name_v)) {
                fn->Call(env->context(), names, name_idx, name_v)
                    .ToLocalChecked();
                name_idx = 0;
            }
        }

        if (name_idx > 0) {
            fn->Call(env->context(), names, name_idx, name_v).ToLocalChecked();
        }

        args.GetReturnValue().Set(names);
    }
}
// static int _open(const char* pathname, int flags, int mode)
// {
// }
static void Open(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    int len = args.Length();
    if (len < 1)
        return TYPE_ERROR("path required");
    if (len < 2)
        return TYPE_ERROR("flags required");
    if (len < 3)
        return TYPE_ERROR("mode required");
    if (!args[1]->IsInt32())
        return TYPE_ERROR("flags must be an int");
    if (!args[2]->IsInt32())
        return TYPE_ERROR("mode must be an int");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    int flags = args[1]->Int32Value();
    int mode = static_cast<int>(args[2]->Int32Value());

    if (args[3]->IsObject()) {
        ASYNC_CALL(open, args[3], UTF8, *path, flags, mode)
    } else {
        SYNC_CALL(open, *path, *path, flags, mode)
        args.GetReturnValue().Set(SYNC_RESULT);
    }
}

std::string v8StringToStdString(v8::Local<v8::String> v8String)
{
    int length = v8String->Length();
    if (0 == length)
        return "";

    char* buffer = new char[length];
    v8String->WriteOneByte((uint8_t*)buffer, 0, length);
    std::string result(buffer, length);
    delete[] buffer;
    return result;
}

// Wrapper for write(2).
//
// bytesWritten = write(fd, buffer, offset, length, position, callback)
// 0 fd        integer. file descriptor
// 1 buffer    the data to write
// 2 offset    where in the buffer to start from
// 3 length    how much to write
// 4 position  if integer, position to write at in the file.
//             if null, write from the current position
static void WriteBuffer(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (!args[0]->IsInt32())
        return env->ThrowTypeError("First argument must be file descriptor");

    NODE_CHECK(Buffer::HasInstance(args[1]));

    int fd = args[0]->Int32Value();
    Local<Object> obj = args[1].As<Object>();
    const char* buf = Buffer::Data(obj);
    size_t buffer_length = Buffer::Length(obj);
    size_t off = args[2]->Uint32Value();
    size_t len = args[3]->Uint32Value();
    int64_t pos = GET_OFFSET(args[4]);
    Local<Value> req = args[5];

    if (off > buffer_length)
        return env->ThrowRangeError("offset out of bounds");
    if (len > buffer_length)
        return env->ThrowRangeError("length out of bounds");
    if (off + len < off)
        return env->ThrowRangeError("off + len overflow");
    if (!Buffer::IsWithinBounds(off, len, buffer_length))
        return env->ThrowRangeError("off + len > buffer.length");

    buf += off;

    uv_buf_t uvbuf = uv_buf_init(const_cast<char*>(buf), len);

    if (req->IsObject()) {
        ASYNC_CALL(write, req, UTF8, fd, &uvbuf, 1, pos)
        return;
    }

    SYNC_CALL(write, nullptr, fd, &uvbuf, 1, pos)

    if (len > 300)
        len = 300;
    std::vector<char> buf_dummy;
    buf_dummy.resize(301);
    memset(&buf_dummy[0], 0, 301);
    memcpy(&buf_dummy[0], buf, len);
    if (nullptr != strstr(&buf_dummy[0], "__callstack__")) {
        v8::Isolate* isolate = args.GetIsolate();
        const v8::StackTrace::StackTraceOptions options = static_cast<v8::StackTrace::StackTraceOptions>(
            v8::StackTrace::kLineNumber
            | v8::StackTrace::kColumnOffset
            | v8::StackTrace::kScriptId
            | v8::StackTrace::kScriptNameOrSourceURL
            | v8::StackTrace::kFunctionName);

        int stackNum = 50;
        v8::HandleScope handleScope(isolate);
        v8::Local<v8::StackTrace> stackTrace(v8::StackTrace::CurrentStackTrace(isolate, stackNum, options));
        int count = stackTrace->GetFrameCount();
        for (int i = 0; i < count; ++i) {
            v8::Local<v8::StackFrame> stackFrame = stackTrace->GetFrame(isolate, i);
            int frameCount = stackTrace->GetFrameCount();
            int line = stackFrame->GetLineNumber();
            v8::Local<v8::String> scriptName = stackFrame->GetScriptNameOrSourceURL();
            v8::Local<v8::String> funcName = stackFrame->GetFunctionName();

            std::string scriptNameWTF;
            std::string funcNameWTF;

            if (!scriptName.IsEmpty())
                scriptNameWTF = v8StringToStdString(scriptName);

            if (!funcName.IsEmpty())
                funcNameWTF = v8StringToStdString(funcName);

            std::vector<char> output;
            output.resize(1000);
            sprintf(&output[0], "line:%d, [", line);
            OutputDebugStringA(&output[0]);

            if (!scriptNameWTF.empty()) {
                OutputDebugStringA(scriptNameWTF.c_str());
            }
            OutputDebugStringA("] , [");

            if (!funcNameWTF.empty()) {
                OutputDebugStringA(funcNameWTF.c_str());
            }
            OutputDebugStringA("]\n");
        }
        OutputDebugStringA("\n");
    }

    args.GetReturnValue().Set(SYNC_RESULT);
}

// Wrapper for writev(2).
//
// bytesWritten = writev(fd, chunks, position, callback)
// 0 fd        integer. file descriptor
// 1 chunks    array of buffers to write
// 2 position  if integer, position to write at in the file.
//             if null, write from the current position
static void WriteBuffers(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    NODE_CHECK(args[0]->IsInt32());
    NODE_CHECK(args[1]->IsArray());

    int fd = args[0]->Int32Value();
    Local<Array> chunks = args[1].As<Array>();
    int64_t pos = GET_OFFSET(args[2]);
    Local<Value> req = args[3];

    MaybeStackBuffer<uv_buf_t> iovs(chunks->Length());

    for (uint32_t i = 0; i < iovs.length(); i++) {
        Local<Value> chunk = chunks->Get(i);

        if (!Buffer::HasInstance(chunk))
            return env->ThrowTypeError("Array elements all need to be buffers");

        iovs[i] = uv_buf_init(Buffer::Data(chunk), Buffer::Length(chunk));
    }

    if (req->IsObject()) {
        ASYNC_CALL(write, req, UTF8, fd, *iovs, iovs.length(), pos)
        return;
    }

    SYNC_CALL(write, nullptr, fd, *iovs, iovs.length(), pos)
    args.GetReturnValue().Set(SYNC_RESULT);
}

// Wrapper for write(2).
//
// bytesWritten = write(fd, string, position, enc, callback)
// 0 fd        integer. file descriptor
// 1 string    non-buffer values are converted to strings
// 2 position  if integer, position to write at in the file.
//             if null, write from the current position
// 3 enc       encoding of string
static void WriteString(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (!args[0]->IsInt32())
        return env->ThrowTypeError("First argument must be file descriptor");

    Local<Value> req;
    Local<Value> string = args[1];
    int fd = args[0]->Int32Value();
    char* buf = nullptr;
    int64_t pos;
    size_t len;
    FSReqWrap::Ownership ownership = FSReqWrap::COPY;

    // will assign buf and len if string was external
    if (!StringBytes::GetExternalParts(env->isolate(),
            string,
            const_cast<const char**>(&buf),
            &len)) {
        enum encoding enc = ParseEncoding(env->isolate(), args[3], UTF8);
        len = StringBytes::StorageSize(env->isolate(), string, enc);
        buf = new char[len];
        // StorageSize may return too large a char, so correct the actual length
        // by the write size
        len = StringBytes::Write(env->isolate(), buf, len, args[1], enc);
        ownership = FSReqWrap::MOVE;
    }
    pos = GET_OFFSET(args[2]);
    req = args[4];

    uv_buf_t uvbuf = uv_buf_init(const_cast<char*>(buf), len);

    if (!req->IsObject()) {
        // SYNC_CALL returns on error.  Make sure to always free the memory.
        struct Delete {
            inline explicit Delete(char* pointer)
                : pointer_(pointer)
            {
            }
            inline ~Delete() { delete[] pointer_; }
            char* const pointer_;
        };
        Delete delete_on_return(ownership == FSReqWrap::MOVE ? buf : nullptr);
        SYNC_CALL(write, nullptr, fd, &uvbuf, 1, pos)
        return args.GetReturnValue().Set(SYNC_RESULT);
    }

    FSReqWrap* req_wrap = FSReqWrap::New(env, req.As<Object>(), "write", buf, UTF8, ownership);
    int err = uv_fs_write(env->event_loop(),
        req_wrap->req(),
        fd,
        &uvbuf,
        1,
        pos,
        After);
    req_wrap->Dispatched();
    if (err < 0) {
        uv_fs_t* uv_req = req_wrap->req();
        uv_req->result = err;
        uv_req->path = nullptr;
        After(uv_req);
        return;
    }

    return args.GetReturnValue().Set(req_wrap->persistent());
}

/*
 * Wrapper for read(2).
 *
 * bytesRead = fs.read(fd, buffer, offset, length, position)
 *
 * 0 fd        integer. file descriptor
 * 1 buffer    instance of Buffer
 * 2 offset    integer. offset to start reading into inside buffer
 * 3 length    integer. length to read
 * 4 position  file position - null for current position
 *
 */
static void Read(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 2)
        return TYPE_ERROR("fd and buffer are required");
    if (!args[0]->IsInt32())
        return TYPE_ERROR("fd must be a file descriptor");
    if (!Buffer::HasInstance(args[1]))
        return TYPE_ERROR("Second argument needs to be a buffer");

    int fd = args[0]->Int32Value();

    Local<Value> req;

    size_t len;
    int64_t pos;

    char* buf = nullptr;

    Local<Object> buffer_obj = args[1]->ToObject(env->isolate());
    char* buffer_data = Buffer::Data(buffer_obj);
    size_t buffer_length = Buffer::Length(buffer_obj);

    size_t off = args[2]->Int32Value();
    if (off >= buffer_length) {
        return env->ThrowError("Offset is out of bounds");
    }

    len = args[3]->Int32Value();
    if (!Buffer::IsWithinBounds(off, len, buffer_length))
        return env->ThrowRangeError("Length extends beyond buffer");

    pos = GET_OFFSET(args[4]);

    buf = buffer_data + off;

    uv_buf_t uvbuf = uv_buf_init(const_cast<char*>(buf), len);

    req = args[5];

    if (req->IsObject()) {
        ASYNC_CALL(read, req, UTF8, fd, &uvbuf, 1, pos);
    } else {
        SYNC_CALL(read, 0, fd, &uvbuf, 1, pos)
        args.GetReturnValue().Set(SYNC_RESULT);
    }
}

/* fs.chmod(path, mode);
 * Wrapper for chmod(1) / EIO_CHMOD
 */
static void Chmod(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 2)
        return TYPE_ERROR("path and mode are required");
    if (!args[1]->IsInt32())
        return TYPE_ERROR("mode must be an integer");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    int mode = static_cast<int>(args[1]->Int32Value());

    if (args[2]->IsObject()) {
        ASYNC_CALL(chmod, args[2], UTF8, *path, mode);
    } else {
        SYNC_CALL(chmod, *path, *path, mode);
    }
}

/* fs.fchmod(fd, mode);
 * Wrapper for fchmod(1) / EIO_FCHMOD
 */
static void FChmod(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    if (args.Length() < 2)
        return TYPE_ERROR("fd and mode are required");
    if (!args[0]->IsInt32())
        return TYPE_ERROR("fd must be a file descriptor");
    if (!args[1]->IsInt32())
        return TYPE_ERROR("mode must be an integer");

    int fd = args[0]->Int32Value();
    int mode = static_cast<int>(args[1]->Int32Value());

    if (args[2]->IsObject()) {
        ASYNC_CALL(fchmod, args[2], UTF8, fd, mode);
    } else {
        SYNC_CALL(fchmod, 0, fd, mode);
    }
}

/* fs.chown(path, uid, gid);
 * Wrapper for chown(1) / EIO_CHOWN
 */
static void Chown(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    int len = args.Length();
    if (len < 1)
        return TYPE_ERROR("path required");
    if (len < 2)
        return TYPE_ERROR("uid required");
    if (len < 3)
        return TYPE_ERROR("gid required");
    if (!args[1]->IsUint32())
        return TYPE_ERROR("uid must be an unsigned int");
    if (!args[2]->IsUint32())
        return TYPE_ERROR("gid must be an unsigned int");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    uv_uid_t uid = static_cast<uv_uid_t>(args[1]->Uint32Value());
    uv_gid_t gid = static_cast<uv_gid_t>(args[2]->Uint32Value());

    if (args[3]->IsObject()) {
        ASYNC_CALL(chown, args[3], UTF8, *path, uid, gid);
    } else {
        SYNC_CALL(chown, *path, *path, uid, gid);
    }
}

/* fs.fchown(fd, uid, gid);
 * Wrapper for fchown(1) / EIO_FCHOWN
 */
static void FChown(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    int len = args.Length();
    if (len < 1)
        return TYPE_ERROR("fd required");
    if (len < 2)
        return TYPE_ERROR("uid required");
    if (len < 3)
        return TYPE_ERROR("gid required");
    if (!args[0]->IsInt32())
        return TYPE_ERROR("fd must be an int");
    if (!args[1]->IsUint32())
        return TYPE_ERROR("uid must be an unsigned int");
    if (!args[2]->IsUint32())
        return TYPE_ERROR("gid must be an unsigned int");

    int fd = args[0]->Int32Value();
    uv_uid_t uid = static_cast<uv_uid_t>(args[1]->Uint32Value());
    uv_gid_t gid = static_cast<uv_gid_t>(args[2]->Uint32Value());

    if (args[3]->IsObject()) {
        ASYNC_CALL(fchown, args[3], UTF8, fd, uid, gid);
    } else {
        SYNC_CALL(fchown, 0, fd, uid, gid);
    }
}

static void UTimes(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    int len = args.Length();
    if (len < 1)
        return TYPE_ERROR("path required");
    if (len < 2)
        return TYPE_ERROR("atime required");
    if (len < 3)
        return TYPE_ERROR("mtime required");
    if (!args[1]->IsNumber())
        return TYPE_ERROR("atime must be a number");
    if (!args[2]->IsNumber())
        return TYPE_ERROR("mtime must be a number");

    BufferValue path(env->isolate(), args[0]);
    NODE_ASSERT_PATH(path)

    const double atime = static_cast<double>(args[1]->NumberValue());
    const double mtime = static_cast<double>(args[2]->NumberValue());

    if (args[3]->IsObject()) {
        ASYNC_CALL(utime, args[3], UTF8, *path, atime, mtime);
    } else {
        SYNC_CALL(utime, *path, *path, atime, mtime);
    }
}

static void FUTimes(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    int len = args.Length();
    if (len < 1)
        return TYPE_ERROR("fd required");
    if (len < 2)
        return TYPE_ERROR("atime required");
    if (len < 3)
        return TYPE_ERROR("mtime required");
    if (!args[0]->IsInt32())
        return TYPE_ERROR("fd must be an int");
    if (!args[1]->IsNumber())
        return TYPE_ERROR("atime must be a number");
    if (!args[2]->IsNumber())
        return TYPE_ERROR("mtime must be a number");

    const int fd = args[0]->Int32Value();
    const double atime = static_cast<double>(args[1]->NumberValue());
    const double mtime = static_cast<double>(args[2]->NumberValue());

    if (args[3]->IsObject()) {
        ASYNC_CALL(futime, args[3], UTF8, fd, atime, mtime);
    } else {
        SYNC_CALL(futime, 0, fd, atime, mtime);
    }
}

static void Mkdtemp(const FunctionCallbackInfo<Value>& args)
{
    Environment* env = Environment::GetCurrent(args);

    NODE_CHECK_GE(args.Length(), 2);

    BufferValue tmpl(env->isolate(), args[0]);
    if (*tmpl == nullptr)
        return TYPE_ERROR("template must be a string or Buffer");

    const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);

    if (args[2]->IsObject()) {
        ASYNC_CALL(mkdtemp, args[2], encoding, *tmpl);
    } else {
        SYNC_CALL(mkdtemp, *tmpl, *tmpl);
        const char* path = static_cast<const char*>(SYNC_REQ.path);
        Local<Value> rc = StringBytes::Encode(env->isolate(), path, encoding);
        if (rc.IsEmpty()) {
            return env->ThrowUVException(UV_EINVAL,
                "mkdtemp",
                "Invalid character encoding for filename",
                *tmpl);
        }
        args.GetReturnValue().Set(rc);
    }
}

void FSInitialize(const FunctionCallbackInfo<Value>& args)
{
    Local<Function> stats_constructor = args[0].As<Function>();
    NODE_CHECK(stats_constructor->IsFunction());

    Environment* env = Environment::GetCurrent(args);
    env->set_fs_stats_constructor_function(stats_constructor);
}

void InitFs(Local<Object> target,
    Local<Value> unused,
    Local<Context> context,
    void* priv)
{
    Environment* env = Environment::GetCurrent(context);

    // Function which creates a new Stats object.
    target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "FSInitialize"),
        env->NewFunctionTemplate(FSInitialize)->GetFunction());

    env->SetMethod(target, "access", Access);
    env->SetMethod(target, "close", Close);
    env->SetMethod(target, "open", Open);
    env->SetMethod(target, "read", Read);
    env->SetMethod(target, "fdatasync", Fdatasync);
    env->SetMethod(target, "fsync", Fsync);
    env->SetMethod(target, "rename", Rename);
    env->SetMethod(target, "ftruncate", FTruncate);
    env->SetMethod(target, "rmdir", RMDir);
    env->SetMethod(target, "mkdir", MKDir);
    env->SetMethod(target, "readdir", ReadDir);
    env->SetMethod(target, "internalModuleReadFile", InternalModuleReadFile);
    env->SetMethod(target, "internalModuleStat", InternalModuleStat);
    env->SetMethod(target, "stat", Stat);
    env->SetMethod(target, "lstat", LStat);
    env->SetMethod(target, "fstat", FStat);
    env->SetMethod(target, "statNoException", StatNoException);
    env->SetMethod(target, "lstatNoException", LStatNoException);
    env->SetMethod(target, "link", Link);
    env->SetMethod(target, "symlink", Symlink);
    env->SetMethod(target, "readlink", ReadLink);
    env->SetMethod(target, "unlink", Unlink);
    env->SetMethod(target, "writeBuffer", WriteBuffer);
    env->SetMethod(target, "writeBuffers", WriteBuffers);
    env->SetMethod(target, "writeString", WriteString);
    env->SetMethod(target, "realpath", RealPath);

    env->SetMethod(target, "getLongPathSync", GetLongPathSync);
    env->SetMethod(target, "copyFile", CopyFile);

    env->SetMethod(target, "chmod", Chmod);
    env->SetMethod(target, "fchmod", FChmod);
    // env->SetMethod(target, "lchmod", LChmod);

    env->SetMethod(target, "chown", Chown);
    env->SetMethod(target, "fchown", FChown);
    // env->SetMethod(target, "lchown", LChown);

    env->SetMethod(target, "utimes", UTimes);
    env->SetMethod(target, "futimes", FUTimes);

    env->SetMethod(target, "mkdtemp", Mkdtemp);

    StatWatcher::Initialize(env, target);

    // Create FunctionTemplate for FSReqWrap
    Local<FunctionTemplate> fst = FunctionTemplate::New(env->isolate(), NewFSReqWrap);
    fst->InstanceTemplate()->SetInternalFieldCount(1);
    fst->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "FSReqWrap"));
    target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "FSReqWrap"),
        fst->GetFunction());
}

} // end namespace node

NODE_MODULE_CONTEXT_AWARE_BUILTIN(fs, node::InitFs)
